// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef MIDI_SOURCE_HH
# define MIDI_SOURCE_HH

# include <deque>
# include <jack/client.hh>
# include <jack/callback_wrapper.hh>
# include <jack/midi_event.hh>
# include <cassert>

namespace funky {

  template <typename D>
  struct midi_source {
  private:
    std::deque<jack::midi_event> queue;
    jack::client* client;
    jack::port<jack_midi_event_t>* port;

    void callback(jack_nframes_t,
		  void *buffer) {
      const size_t size = jack_midi_get_event_count(buffer);
      for (size_t i = 0; i < size; ++i) {
	jack_midi_event_t e;
	::jack_midi_event_get(&e, buffer, i);
	queue.push_back(&e);
      }
    }

  public:
    ~midi_source() {
      delete port;
    }

    midi_source(jack::client& client):
      client(&client),
      port(client.register_port<jack_midi_event_t>("midi_source",
						   JackPortIsInput,
						   512)) {
      assert(port != NULL);
      port->set_callback(jack::fun(this, &midi_source::callback));
    }

    midi_source(const midi_source& other):
      queue(other.queue),
      client(other.client) {
      assert(other.port != NULL);
      port = new jack::port<jack_midi_event_t>(*other.port);
      port->set_callback(jack::fun(this, &midi_source::callback));
    }

    midi_source() = delete;

    midi_source(midi_source&& other):
      queue(std::move(other.queue)),
      client(other.client),
      port(other.port) {
      other.port = NULL;
      assert(port != NULL);
      port->set_callback(jack::fun(this, &midi_source::callback));
    }

    midi_source& operator=(const midi_source& other) {
      return *this = midi_source(other);
    }

    midi_source& operator=(midi_source&& other) {
      std::swap(queue, other.queue);
      std::swap(client, other.client);
      std::swap(port, other.port);
      assert(port != NULL);
      port->set_callback(jack::fun(this, &midi_source::callback));
      assert(other.port != NULL);
      other.port->set_callback(jack::fun(&other, &midi_source::callback));
      return *this;
    }

    jack::midi_event operator*() const {
      if (queue.empty())
	return jack::midi_event{};
      jack::midi_event ret = queue.front();
      const_cast<midi_source*>(this)->queue.pop_front();
      return ret;
    }

    midi_source& operator+=(const D&) {
      return *this;
    }

    midi_source& operator++() {
      return *this;
    }
  };

  template <typename D>
  struct signal_traits<midi_source<D> > {
    typedef D duration;
    typedef jack::midi_event sample;
  };

}

#endif
